/*
* PROJECT: Happy New Year
* VERSION: 1.0
* AUTHOR: Mattias Thell
* URL: http://www.mattiasthell.com
*
* */

#include "World.hpp"

#include <assert.h>
#include <stdlib.h> 
#include <sstream>
#include <time.h>
#include <fstream>

#include <hgefont.h>

#include "Actor.hpp"
#include "ActorManager.hpp"
#include "Settings.hpp"
#include "Rocket.hpp"
#include "Explosion.hpp"
#include "RocketSpawner.hpp"
#include "TargetRocket.hpp"
#include "Text.hpp"
#include "AudioManager.hpp"

namespace hny {

	

	World::World(HGE* hge)
		:mHge(hge), mScore(0), mCombos(0), mHighestCombo(0), mMultiplier(1.0f), 
		mMenuActive(true), mGameStarted(false), mGameEnded(false), mFinished(false), 
		mSplashAccum(0.0f), mSplashAlpha(0.0f), mMusicPlaying(false), mHavePlayedWarning(false)
	{
	}

	World::~World()
	{
		delete mRocketSpawner;
		delete mMenu;

		
		TextureMap::iterator it;
		for(it = mTextures.begin(); it != mTextures.end(); it++)
			mHge->Texture_Free(it->second);
	}

	void World::Initialize() {

		LoadTextures();
		LoadEffects();

		LoadHighScores();

		mMenu = new Menu(this);

		mRocketSpawner = new RocketSpawner(this);

		mTimeLeft = Settings::GetInstance()->StartingTime;

		mSplashTime = Settings::GetInstance()->SplashTime;
		mShowSplash = Settings::GetInstance()->ShowSplash;

		mVolumeMusic = Settings::GetInstance()->VolumeMusic;
		mVolumeSound = Settings::GetInstance()->VolumeSound;

		mMusic = AudioManager::GetInstance()->GetSound("music");
		mMusic->SetVolume(mVolumeMusic);

		Settings::GetInstance()->ComboFont = new hgeFont("Content/font1.fnt");
		
		mParticleSprites = new hgeAnimation(mTextures["particles"], 16, 1.0f, 0, 0, 32, 32);
		mParticleSprites->SetHotSpot(16,16);
		mParticleSprites->SetFrame(7);

		mBackgroundSprite = new hgeSprite(mTextures["background"],0,0,1024,768);

		mSidebarSpriteLeft  = new hgeSprite(mTextures["black_alpha"],0,0,150,768);
		mSidebarSpriteRight = new hgeSprite(mTextures["black_alpha"],0,0,150,768);

		mLogoSprite = new hgeSprite(mTextures["logo"], 0, 0, 550, 143);
		mLogoSprite->SetHotSpot(275, 71);

		mSplashSprite = new hgeSprite(mTextures["splash"], 0, 0, 500, 558);
		mSplashSprite->SetHotSpot(250, 279);

		/* EXPLOSION PARTICLE INFO */
		hgeParticleSystemInfo ExpInfo;

		ExpInfo.sprite = mParticleSprites;
		ExpInfo.sprite->SetBlendMode(BLEND_ALPHAADD);
		ExpInfo.nEmission = 100;
		ExpInfo.fLifetime = 0.2f;

		ExpInfo.fParticleLifeMin = 0.5f;
		ExpInfo.fParticleLifeMax = 1.0f;

		ExpInfo.fDirection = 0.0f;
		ExpInfo.fSpread = 2.0*M_PI;
		ExpInfo.bRelative = false;

		ExpInfo.fSpeedMin = 100.0f;
		ExpInfo.fSpeedMax = 150.0f;

		ExpInfo.fGravityMin = 0.0f;
		ExpInfo.fGravityMax = 0.0f;

		ExpInfo.fRadialAccelMin = 0.0f;
		ExpInfo.fRadialAccelMax = 0.0f;

		ExpInfo.fTangentialAccelMin = 0.0f;
		ExpInfo.fTangentialAccelMax = 0.0f;

		ExpInfo.fSizeStart = 0.5f;
		ExpInfo.fSizeEnd = 0.7f;
		ExpInfo.fSizeVar = 1.0f;

		ExpInfo.fSpinStart = 0.0f;
		ExpInfo.fSpinEnd = 0.0f;
		ExpInfo.fSpinVar = 0.0f;

		ExpInfo.colColorStart = hgeColor(0.8f, 0.65f, 0.1f, 1.0f);
		ExpInfo.colColorEnd = hgeColor(0.3f, 0.15f, 0.8f, 0.0f);
		ExpInfo.fColorVar = 1.0f;
		ExpInfo.fAlphaVar = 0.5f;

		/* ROCKET PARTICLE INFO */
		hgeParticleSystemInfo RocketParticleInfo;

		RocketParticleInfo.sprite = mParticleSprites;
		RocketParticleInfo.nEmission = 200;
		RocketParticleInfo.fLifetime = -1.0f;

		RocketParticleInfo.fParticleLifeMin = 1.0f;
		RocketParticleInfo.fParticleLifeMax = 2.0f;

		RocketParticleInfo.fDirection = 0.0f;
		RocketParticleInfo.fSpread = 0.0f;
		RocketParticleInfo.bRelative = true;

		RocketParticleInfo.fSpeedMin = 50.0f;
		RocketParticleInfo.fSpeedMax = 75.0f;

		RocketParticleInfo.fGravityMin = 0.0f;
		RocketParticleInfo.fGravityMax = 0.0f;

		RocketParticleInfo.fRadialAccelMin = 0.0f;
		RocketParticleInfo.fRadialAccelMax = 0.0f;

		RocketParticleInfo.fTangentialAccelMin = 0.0f;
		RocketParticleInfo.fTangentialAccelMax = 0.0f;

		RocketParticleInfo.fSizeStart = 0.3f;
		RocketParticleInfo.fSizeEnd = 0.1f;
		RocketParticleInfo.fSizeVar = 1.0f;

		RocketParticleInfo.fSpinStart = 0.0f;
		RocketParticleInfo.fSpinEnd = 0.0f;
		RocketParticleInfo.fSpinVar = 0.0f;

		RocketParticleInfo.colColorStart = Settings::GetInstance()->RocketColorStart;
		RocketParticleInfo.colColorEnd = Settings::GetInstance()->RocketColorEnd;
		RocketParticleInfo.fColorVar = 1.0f;
		RocketParticleInfo.fAlphaVar = 0.5f;

		/* PLAYAER ROCKET INFO */
		hgeParticleSystemInfo PlayerRocketParticleInfo = RocketParticleInfo;
		PlayerRocketParticleInfo.nEmission = 200;
		PlayerRocketParticleInfo.colColorStart = Settings::GetInstance()->PlayerRocketColorStart;
		PlayerRocketParticleInfo.colColorEnd = Settings::GetInstance()->PlayerRocketColorEnd;
		PlayerRocketParticleInfo.fSizeStart = 0.3f;
		PlayerRocketParticleInfo.fSizeEnd = 0.2f;

		/* BONUS ROCKET INFO */
		hgeParticleSystemInfo BonusRocketParticleInfo = PlayerRocketParticleInfo;
		BonusRocketParticleInfo.nEmission = 200;
		BonusRocketParticleInfo.colColorStart = Settings::GetInstance()->BonusRocketColorStart;
		BonusRocketParticleInfo.colColorEnd = Settings::GetInstance()->BonusRocketColorEnd;
		BonusRocketParticleInfo.fSizeStart = 0.3f;
		BonusRocketParticleInfo.fSizeEnd = 0.2f;

		/* DUAL ROCKET INFO */
		hgeParticleSystemInfo DualRocketParticleInfo = BonusRocketParticleInfo;
		DualRocketParticleInfo.colColorStart = Settings::GetInstance()->DualRocketColorStart;
		DualRocketParticleInfo.colColorEnd = Settings::GetInstance()->DualRocketColorEnd;
		DualRocketParticleInfo.fSizeStart = 0.4f;
		DualRocketParticleInfo.fSizeEnd = 0.2f;


		Settings::GetInstance()->RocketParticleInfo = RocketParticleInfo;
		Settings::GetInstance()->ExplosionParticleInfo = ExpInfo;
		Settings::GetInstance()->PlayerRocketParticleInfo = PlayerRocketParticleInfo;
		Settings::GetInstance()->BonusRocketParticleInfo = BonusRocketParticleInfo;
		Settings::GetInstance()->DualRocketParticleInfo = DualRocketParticleInfo;

		srand( (unsigned int)time(0));
		mHge->Random_Seed((int)time(0));
	}

	void World::LoadTextures() {

		mTextures["background"] = mHge->Texture_Load("Content/hny_bg_full_1024.png");
		mTextures["black_alpha"] = mHge->Texture_Load("Content/sidebar_10.png");
		mTextures["particles"] = mHge->Texture_Load("Content/particles.png");
		mTextures["cursor"] = mHge->Texture_Load("Content/cursor.png");
		mTextures["logo"] = mHge->Texture_Load("Content/hny_Logo.png");
		mTextures["splash"] = mHge->Texture_Load("Content/wws_logo.png");
	}

	void World::LoadEffects()
	{
		AudioManager::GetInstance()->AddAudio("Content/hny_music.mp3", "music", true);
		AudioManager::GetInstance()->AddAudio("Content/exp1.wav", "exp1");
		AudioManager::GetInstance()->AddAudio("Content/exp2.wav", "exp2");
		AudioManager::GetInstance()->AddAudio("Content/exp3.wav", "exp3");
		
		AudioManager::GetInstance()->AddAudio("Content/rocket1.wav", "rocket1");
		AudioManager::GetInstance()->AddAudio("Content/rocket2.wav", "rocket2");

		AudioManager::GetInstance()->AddAudio("Content/warning.wav", "warning");

		mVolumeSound = Settings::GetInstance()->VolumeSound;
		mExplosionSounds.push_back(AudioManager::GetInstance()->GetSound("exp1"));
		mExplosionSounds[0]->SetVolume(mVolumeSound);
		mExplosionSounds.push_back(AudioManager::GetInstance()->GetSound("exp2"));
		mExplosionSounds[1]->SetVolume(mVolumeSound);

		mRocketSounds.push_back(AudioManager::GetInstance()->GetSound("rocket1"));
		mRocketSounds[0]->SetVolume(mVolumeSound);
		mRocketSounds.push_back(AudioManager::GetInstance()->GetSound("rocket2"));
		mRocketSounds[1]->SetVolume(mVolumeSound);

		// This sound is a little annoying  so decrease its volume another 20%
		AudioManager::GetInstance()->GetSound("warning")->SetVolume(mVolumeSound*0.2f);

	}

	bool World::Update(float dt) 
	{
		/////////////////////////////////////////////////
		// Update the audio 

		AudioManager::GetInstance()->Update();

		if(mHge->Input_KeyDown(HGEK_ADD)) {
			mVolumeMusic += 0.05f;
		}
		if(mHge->Input_KeyDown(HGEK_SUBTRACT)) {
			mVolumeMusic -= 0.05f;
		}

		mMusic->SetVolume(mVolumeMusic);

		if(!mMusic->IsPlaying())
			mMusic->Play();

		///////////////////////////////////////////////////
		// Show the splash when starting

		if(mShowSplash) {
			if(mSplashAccum <= mSplashTime) {
				mSplashAccum += dt;

				if(mSplashAccum <= 1.0f)
					mSplashAlpha = mSplashAccum;

				if(mSplashAccum > mSplashTime-1.0f)
					mSplashAlpha = mSplashTime / mSplashAccum;

				return false;
			}
			else {
				mShowSplash = false;
			}
		}

		bool quit = false;

		/////////////////////////////////////////////////
		// Show the menu?

		static bool prev = false;
		bool st = mHge->Input_GetKeyState(HGEK_ESCAPE);
		if (st && !prev) {
			if(mMenuActive && mGameStarted)
				mMenuActive = false;
			else
				mMenuActive = true;
		}
		prev = st;

		

		if(mMenuActive) {
			//////////////////////////////////////////////
			// MENU

			quit = mMenu->Update(dt);

		}
		else 
		{
			//////////////////////////////////////////////
			// GAME

			if(!mFinished) {

				mTimeLeft -= dt;

				if(!mHavePlayedWarning && mTimeLeft <= Settings::GetInstance()->TimeWarning2) {
					AudioManager::GetInstance()->GetSound("warning")->Play();
					mHavePlayedWarning = true;
				}

				if(mTimeLeft <= 0.0) {
					mTimeLeft = 0.0f;

					EndGame();
				}

				if(mHge->Input_KeyDown(HGEK_LBUTTON)) {
					
					float x, y;
					mHge->Input_GetMousePos(&x, &y);

					float v = Settings::GetInstance()->PlayerRocketVelocity;

					hgeVector start(1024/2, 768);
					hgeVector target(x, y);
					hgeVector vel = target - start;
					vel.Normalize();
					vel *= v;

					hgeParticleSystemInfo pi = Settings::GetInstance()->PlayerRocketParticleInfo;
					ActorType rocket(new TargetRocket(this, start, vel, 10, pi, target));
					
					ActorManager::GetInstance()->AddActor(rocket);

					// When a rocket is fired, decrease the penalty.
					// Thus, prevent spamming of rockets
					mMultiplier -= Settings::GetInstance()->MultiplierRocketFirePenalty;
					
					// We need a minimum, otherwise we wouldn't be able to recuperate
					// since the score is multiplied with the Multiplier
					float mmin = Settings::GetInstance()->MultiplierPenaltyMin;
					if(mMultiplier < mmin)
						mMultiplier = mmin;

					// Don't let the Multiplier fall below 1.0 in the normal case
					if(mMultiplier > 1.0f) {
						float multDelta = Settings::GetInstance()->MultiplierDecreasePerSecond * dt;

						if(mMultiplier > multDelta)
							mMultiplier -= multDelta;
					}

					// Play a sound
					PlayRocketSound();
				}

				// Gief more rockets!!
				mRocketSpawner->SpawnRockets(dt);

				CollideActors();

			}


			ActorManager::GetInstance()->Update(dt);

			// Wait for all actors to die and then update and save the high scores
			if(mFinished && ActorManager::GetInstance()->GetActors().size() <= 0) {
				mMenu->DisplayScore(true);
				mMenuActive = true;

				UpdateHighScores();
				SaveHighScores();
			}

			// Update the GUI text
			mTextCollection.ActiveRocketsValue = ActorManager::GetInstance()->GetActors().size();
			mTextCollection.ScoreValue = mScore;
			mTextCollection.HighestComboValue = mHighestCombo;
			mTextCollection.TimeValue = mTimeLeft;
			mTextCollection.MultiplierValue = (int)(mMultiplier * 100.0f); // As percentage
		}

		return quit;
	}

	bool World::Draw() 
	{
		float width = (float)Settings::GetInstance()->SystemScreenWidth;
		float height = (float)Settings::GetInstance()->SystemScreenHeight;

		mHge->Gfx_BeginScene();

		if(mShowSplash) {

			DWORD c = mSplashSprite->GetColor();
			hgeColor cc(c);
			
			cc.a = mSplashAlpha;
			mSplashSprite->SetColor(cc.GetHWColor());
			mSplashSprite->RenderEx(width/2, height/2, 0.0f, 0.75f);

			mHge->Gfx_EndScene();
			return false;
		}

		// Draw the background
		mBackgroundSprite->Render(0,0);

		// Draw all actors
		foreach(ActorType a, ActorManager::GetInstance()->GetActors()) {
			if(a->IsAlive())
				a->Draw();
		}

		// Draw sidebars
		int screenWidth = mHge->System_GetState(HGE_SCREENWIDTH);
		mSidebarSpriteLeft->Render(0,0);
		mSidebarSpriteRight->Render(screenWidth - mSidebarSpriteRight->GetWidth(), 0);

		if(mMenuActive) {
			// Draw the menu
			mMenu->Draw();

			mLogoSprite->Render(width/2, 100);
		}
		else {
			// Draw text
			mTextCollection.Draw();
		}

		mHge->Gfx_EndScene();

		return false;
	}

	void World::CollideActors() 
	{
		foreach(ActorType a, ActorManager::GetInstance()->GetActors()) {
			foreach(ActorType b, ActorManager::GetInstance()->GetActors()) {
				
				// Is it necessary to check for collisions?
				if(!a->IsAlive() || !b->IsAlive() || 
				   !a->IsCollidable() || !b->IsCollidable() ||
				   a->IsDying() || b->IsDying() )
					continue;

				// We only care about collisions between explosions and rockets
				if(a->GetKind() == Kind_Explosion && b->GetKind() == Kind_Rocket) {
					if(a->CollidesWith(*b)) {
						Explosion* exp = static_cast<Explosion*>(a.get());
						Rocket* rocket = static_cast<Rocket*>(b.get());

						rocket->StartDying();
						hgeVector bPos = rocket->GetPosition();

						unsigned int combos = exp->GetCombo();
						unsigned int newCombo = combos+1;

						mCombos = newCombo;

						int score = rocket->GetScore();
						CalculateScore(score);
						
						SpawnExplosion(bPos, newCombo);
						mMultiplier += Settings::GetInstance()->MultiplierAddition;
							
						if(newCombo >= (unsigned int)Settings::GetInstance()->ShowCombosAfterAmount)
							SpawnComboText(bPos, newCombo);

						if(mHighestCombo < newCombo)
							mHighestCombo = newCombo;

					}
				}
			}
		}
	}

	void World::SpawnExplosion(hgeVector position, int combo, float velocity, float radius) {

		ActorType exp(new Explosion(this, position, combo, velocity, radius));
		ActorManager::GetInstance()->AddActor(exp);

		PlayExplosionSound();
	}

	void World::SpawnComboText(const hgeVector pos, int combo)
	{
		// It would be nice to make the calculation of color 
		// more "dynamic", but meh...
		hgeColor c;
		switch(combo) {
			case 2:
				c = hgeColor(1.0f, 1.0f, 1.0f, 1.0f); break;
			case 3:
				c = hgeColor(1.0f, 1.0f, 0.7f, 1.0f); break;
			case 4:
				c = hgeColor(1.0f, 1.0f, 0.2f, 1.0f); break;
			case 5:
				c = hgeColor(1.0f, 0.5f, 0.2f, 1.0f); break;
			case 6:
				c = hgeColor(1.0f, 0.2f, 0.2f, 1.0f); break;
			case 7:
				c = hgeColor(1.0f, 0.0f, 0.0f, 1.0f); break;
			default:
				c = hgeColor(1.0f, 1.0f, 1.0f, 1.0f);
		}

		std::stringstream ss;
		ss << "x " << combo;
		ActorType t(new Text(this, ss.str(), pos, c));
		ActorManager::GetInstance()->AddActor(t);
	}

	void World::CalculateScore(int score)
	{
		mScore += (unsigned int)(score * mCombos * mMultiplier);
	}

	void World::StartNewGame()
	{
		Settings::GetInstance()->LoadSettings();

		mScore = 0;
		mCombos = 0;
		mHighestCombo = 0;
		mMultiplier = 1.0f;
		mTimeLeft = Settings::GetInstance()->StartingTime;

		ActorManager::GetInstance()->EraseAll();

		mMenuActive = false;
		mFinished = false;
		mGameStarted = true;
		mHavePlayedWarning = false;
	}

	void World::EndGame() 
	{
		mFinished = true;
		mGameStarted = false;
		
		foreach(ActorType a, ActorManager::GetInstance()->GetActors()) 
		{
			if(a->GetKind() == Kind_Rocket) {
				a->Kill();
				SpawnExplosion(a->GetPosition(), 0);
			}
		}
		
	}

	void World::LoadHighScores() 
	{
		std::ifstream file(Settings::GetInstance()->ScoreFile.c_str());
		if(file.is_open()) {
			int nItems;
			file >> nItems;

			for(int i = 0; i < nItems; i++) {
				unsigned int score;
				time_t time;
				file >> score;
				file >> time;

				mHighScores.push_back(ScoreEntry(score, time));
			}

			file.close();
		}
		else { // No score file exists, so create a default score.

			time_t ltime;
			time(&ltime);

			mHighScores.push_back(ScoreEntry(100, ltime));
		}

		std::sort(mHighScores.rbegin(), mHighScores.rend(), ScorePredicateLess());
	}

	void World::SaveHighScores()
	{
		std::ofstream of(Settings::GetInstance()->ScoreFile.c_str());
		if(of.is_open()) {
			of << mHighScores.size();
			of << "\n";

			foreach(ScoreEntry s, mHighScores) {
				of << s.Score;
				of << " ";
				of << s.Time;
				of << "\n";
			}

			of.close();
		}
	}

	void World::UpdateHighScores() 
	{
		time_t t;
		time(&t);

		ScoreVector::iterator i;
		for(i = mHighScores.begin(); i != mHighScores.end(); i++) {
			(*i).New = false;
		}
		
		mHighScores.push_back(ScoreEntry(mScore, t, true));

		std::sort(mHighScores.rbegin(), mHighScores.rend(), ScorePredicateLess());
	}

	ScoreVector World::GetHighScores()
	{
		return mHighScores;
	}

	void World::PlayRocketSound()
	{
		int r = mHge->Random_Int(0, (int)mRocketSounds.size()-1);
		mRocketSounds[r]->Play();
	}

	void World::PlayExplosionSound()
	{
		int r = mHge->Random_Int(0, (int)mExplosionSounds.size()-1);
		mExplosionSounds[r]->Play();
	}

	HTEXTURE World::GetTexture(const std::string& name)
	{
		TextureMap::iterator it = mTextures.find(name);
		if(it == mTextures.end())
			return 0;

		return it->second;
	}

	
}